//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include "CCallbackContext.h"
#include <elacs.h>

EXTERN_C int CDECL printf(const char *fmt,...);

ELAPI_(PCallbackEvent) _Impl_CallbackSink_AllocCallbackEvent(MemorySize size)
{
    // TODO: optimize
    return (PCallbackEvent)_CMemoryHeap_Alloc(size, FALSE);
}

ELAPI_(void) _Impl_CallbackSink_FreeCallbackEvent(PVoid pCallbackEvent)
{
    // TODO: optimize

    ((PCallbackEvent)pCallbackEvent)->~_EzCallbackEvent();
    _CMemoryHeap_Free(pCallbackEvent);
}

//
//  class CCallbackContext
//
UInt32 CCallbackContext::AddRef()
{
    Int32 nRef = m_cRef.Increment();

//    printf("CCallbackContext::AddRef(%d) %x\n", nRef, this);
    return (UInt32)nRef;
}

UInt32 CCallbackContext::Release()
{
    Int32 nRef = m_cRef.Decrement();

//    printf("CCallbackContext::Release(%d) %x\n", nRef, this);
    if (0 == nRef) {
        assert(m_currentEvents.IsEmpty());
        delete this;
    }

    return nRef;
}

PInterface CCallbackContext::Probe(REIID riid)
{
    if (riid == EIID_IInterface) {
        return (PInterface)this;
    }
    return NULL;
}

ECode CCallbackContext::Aggregate(AggregateType type, PInterface pObj)
{
    return E_NOT_IMPLEMENTED;
}

ECode CCallbackContext::GetDomain(PInterface* ppObj)
{
    return E_NOT_IMPLEMENTED;
}

void CCallbackContext::PushEvent(DLinkNode* pEvent)
{
    void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);
    m_currentEvents.InsertNext(pEvent);
    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);
}

DLinkNode* CCallbackContext::PopEvent()
{
    void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);
    DLinkNode* pCallbackEvent = m_currentEvents.First();
    pCallbackEvent->Detach();
    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);
    return pCallbackEvent;
}

Boolean CCallbackContext::IsExistEvent(PCallbackEvent pEvent)
{
    return IsExistEvent(pEvent->m_pSender, pEvent->m_id,
                            pEvent->m_pHandlerThis, pEvent->m_pHandlerFunc);
}

Boolean CCallbackContext::IsExistEvent(
    PInterface pSender,
    CallbackEventId id,
    PVoid pHandlerThis,
    PVoid pHandlerFunc)
{
    PCallbackEvent pCurtEvent;
    void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);
    ForEachDLinkNode(PCallbackEvent, pCurtEvent, &m_currentEvents) {
        if ( (pSender == pCurtEvent->m_pSender || pSender == (PInterface)-1)
            && (id == pCurtEvent->m_id || id == (CallbackEventId)-1)
            && (pHandlerThis == pCurtEvent->m_pHandlerThis
                                               || pHandlerThis == (PVoid)-1)
            && (pHandlerFunc == pCurtEvent->m_pHandlerFunc
                                               || pHandlerFunc == (PVoid)-1) ) {
            _Elastos_CriticalSection_Leave(&m_queueLock, pContext);
            return TRUE;
        }
    }

    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);
    return FALSE;
}

ECode CCallbackContext::PostCallbackEvent(PCallbackEvent pCallbackEvent)
{
    assert(pCallbackEvent);

    Void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);

    pCallbackEvent->AddRef();
    if (m_bExitRequested ||
            m_bRequestToQuit ||
            m_Status >= AppletStatus_Finishing) {

        _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

        pCallbackEvent->m_Status = CallingStatus_Cancelled;
        pCallbackEvent->m_bCompleted = TRUE;
        if (pCallbackEvent->m_pSyncEvent) pCallbackEvent->m_pSyncEvent->Notify(1);
        pCallbackEvent->Release();

        return E_APPLET_ALREADY_FINISHED;
    }

    Boolean bNeedNotify = m_eventQueue.IsEmpty();

    ECode ec;
    PCallbackEvent pPrevCBEvent;
    PCallbackEvent pCancelingCBEvent;

    if (pCallbackEvent->m_flags & CallbackEventFlag_Duplicated) {
        CallbackEventFlags highestFlag =
            pCallbackEvent->m_flags & CallbackEventFlag_PriorityMask;
        ForEachDLinkNodeReversely(PCallbackEvent, pPrevCBEvent, &m_eventQueue) {
            if (pCallbackEvent->m_id == pPrevCBEvent->m_id
              && pCallbackEvent->m_pSender == pPrevCBEvent->m_pSender
              && pCallbackEvent->m_pHandlerThis == pPrevCBEvent->m_pHandlerThis
              && pCallbackEvent->m_pHandlerFunc == pPrevCBEvent->m_pHandlerFunc) {
                PCoalesceEventFunc pUserFunc = (PCoalesceEventFunc)pCallbackEvent->m_pCoalesceFunc;
                if (pUserFunc) {
                    ec = pUserFunc((PVoid*)&pPrevCBEvent, (PVoid*)&pCallbackEvent);
                    if (FAILED(ec)) {
                        if (ec == E_CANCLE_BOTH_EVENTS) {
                            pCallbackEvent->Release();
                            pPrevCBEvent->Detach();
                            pPrevCBEvent->Release();
                            m_nEventsCount--;
                            bNeedNotify = FALSE;
                            goto Next;
                        }
                        continue;
                    }
                }
                highestFlag =
                    pPrevCBEvent->m_flags & CallbackEventFlag_PriorityMask;
                pCancelingCBEvent = pPrevCBEvent;
                pPrevCBEvent = (PCallbackEvent)pPrevCBEvent->Next();
                pCancelingCBEvent->Detach();
                m_nEventsCount--;
                pCancelingCBEvent->Release();
            }
        }
        if ((pCallbackEvent->m_flags & CallbackEventFlag_PriorityMask)
                > highestFlag) {
            pCallbackEvent->m_flags &= ~CallbackEventFlag_PriorityMask;
            pCallbackEvent->m_flags |= highestFlag;
        }
    }

    ForEachDLinkNodeReversely(PCallbackEvent, pPrevCBEvent, &m_eventQueue) {
        if ((pPrevCBEvent->m_flags & CallbackEventFlag_PriorityMask)
            <= (pCallbackEvent->m_flags & CallbackEventFlag_PriorityMask)) {
            pPrevCBEvent->InsertNext(pCallbackEvent);
            m_nEventsCount++;
            goto Next;
        }
    }

    m_eventQueue.InsertFirst(pCallbackEvent);
    m_nEventsCount++;

Next:
    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

    if (bNeedNotify) {
        m_pThreadEvent->Notify(ThreadEvent_NewArrival);
    }

    return NOERROR;
}

ECode CCallbackContext::SendCallbackEvent(
    PCallbackEvent pCallbackEvent,
    Millisecond32 timeOut)
{
    ECode _ec_;
    AppletStatus appletStatus = 0;
    WaitResult _wr_;
    PInterface _pOrgCallbackContext_;
    Boolean bCancelled = FALSE;

    _CThread_GetLocalObject(TL_ORG_CALLBACK_SLOT, &_pOrgCallbackContext_);

    if (_pOrgCallbackContext_) {
        appletStatus = _Impl_CallbackSink_GetStatus(_pOrgCallbackContext_);
        _pOrgCallbackContext_->Release();
    }

    if (_pOrgCallbackContext_ && appletStatus > AppletStatus_Created) {

        if (NOERROR != (_ec_ = _Impl_CallbackSink_GetThreadEvent(
                        _pOrgCallbackContext_, &pCallbackEvent->m_pSyncEvent))) {
        }
        else if (NOERROR != (_ec_ = _Impl_CallbackSink_PostCallbackEvent(
                            this, pCallbackEvent))) {
        }
        else if (NOERROR != (_ec_ = _Impl_CallbackSink_WaitForCallbackEvent(
                            _pOrgCallbackContext_, timeOut, &_wr_,
                            (Boolean*)&pCallbackEvent->m_bCompleted,
                            CallbackEventFlag_SyncCall))) {
        }
        else {
            if (WaitResult_OK == _wr_) _ec_ = NOERROR;
            else if (WaitResult_Interrupted == _wr_) {
                _ec_ = E_INTERRUPTED;
                bCancelled = TRUE;
            }
            else if (WaitResult_TimedOut == _wr_) {
                _ec_= E_TIMED_OUT;
                bCancelled = TRUE;
            }
        }
    }
    else {
        EventState _es_;

        if (NOERROR !=
            (_ec_ = CEvent::New(TRUE, FALSE, &pCallbackEvent->m_pSyncEvent))) {
        }
        else if (NOERROR !=
            (_ec_ = _Impl_CallbackSink_PostCallbackEvent(
                                this, pCallbackEvent))) {
        }
        else if (NOERROR !=
            (_ec_ = pCallbackEvent->m_pSyncEvent->TryWait(timeOut, &_wr_, &_es_))) {
        }
        else {
            if (WaitResult_OK == _wr_) _ec_ = NOERROR;
            else if (WaitResult_Interrupted == _wr_) {
                _ec_ = E_INTERRUPTED;
                bCancelled = TRUE;
            }
            else if (WaitResult_TimedOut == _wr_) {
                _ec_= E_TIMED_OUT;
                bCancelled = TRUE;
            }
        }
    }

    if (pCallbackEvent->m_Status == CallingStatus_Cancelled) {
        _ec_ = E_CALLBACK_CANCELED;
    }

    if (bCancelled) {
        pCallbackEvent->m_Status = CallingStatus_Cancelled;
    }

    return _ec_;
}

ECode CCallbackContext::RequestToFinish(AppletFinish flag)
{
    ThreadEvent eventType = ThreadEvent_RequestToQuit;
    if (flag == AppletFinish_Nice) {
        m_bRequestToQuit = TRUE;
        eventType = ThreadEvent_RequestToQuit;
    }
    else if (flag == AppletFinish_ASAP) {
        m_bRequestToQuit = FALSE;
        eventType = ThreadEvent_ForceToQuit;
    }
    else return E_NOT_IMPLEMENTED;

    m_bExitRequested = TRUE;
    m_pThreadEvent->Notify(ThreadEvent_NewArrival);
    return NOERROR;
}

ECode CCallbackContext::CancelAllPendingCallbacks(
    PInterface pSender)
{
    register PCallbackEvent pCBEvent;
    PCallbackEvent pCancelingCBEvent;

    void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);
    ForEachDLinkNode(PCallbackEvent, pCBEvent, &m_eventQueue) {
        if (pCBEvent->m_pSender == pSender) {
            pCancelingCBEvent = pCBEvent;
            pCBEvent = (PCallbackEvent)pCancelingCBEvent->Prev();
            pCancelingCBEvent->Detach();
            --m_nEventsCount;

            pCancelingCBEvent->m_Status = CallingStatus_Cancelled;
            pCancelingCBEvent->m_bCompleted = TRUE;
            if (pCancelingCBEvent->m_pSyncEvent) pCancelingCBEvent->m_pSyncEvent->Notify(1);
            pCancelingCBEvent->Release();
        }
    }

    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

    return NOERROR;
}

ECode CCallbackContext::CancelPendingCallback(
    PInterface pSender,
    CallbackEventId id)
{
    register PCallbackEvent pCBEvent;
    PCallbackEvent pCancelingCBEvent;

    void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);
    ForEachDLinkNode(PCallbackEvent, pCBEvent, &m_eventQueue) {
        if (pCBEvent->m_pSender == pSender
            && pCBEvent->m_id == id) {
            pCancelingCBEvent = pCBEvent;
            pCBEvent = (PCallbackEvent)pCancelingCBEvent->Prev();
            pCancelingCBEvent->Detach();
            --m_nEventsCount;

            pCancelingCBEvent->m_Status = CallingStatus_Cancelled;
            pCancelingCBEvent->m_bCompleted = TRUE;
            if (pCancelingCBEvent->m_pSyncEvent) pCancelingCBEvent->m_pSyncEvent->Notify(1);
            pCancelingCBEvent->Release();
        }
    }

    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

    return NOERROR;
}

ECode CCallbackContext::CancelCallbackEvents(
    PInterface pSender,
    CallbackEventId id,
    PVoid pHandlerThis,
    PVoid pHandlerFunc)
{
    register PCallbackEvent pCBEvent;
    PCallbackEvent pCancelingCBEvent;

    void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);
    ForEachDLinkNode(PCallbackEvent, pCBEvent, &m_eventQueue) {
        if (pCBEvent->m_pSender == pSender
            && pCBEvent->m_id == id
            && pCBEvent->m_pHandlerThis == pHandlerThis
            && pCBEvent->m_pHandlerFunc == pHandlerFunc) {
            pCancelingCBEvent = pCBEvent;
            pCBEvent = (PCallbackEvent)pCancelingCBEvent->Prev();
            pCancelingCBEvent->Detach();
            --m_nEventsCount;

            pCancelingCBEvent->m_Status = CallingStatus_Cancelled;
            pCancelingCBEvent->m_bCompleted = TRUE;
            if (pCancelingCBEvent->m_pSyncEvent) pCancelingCBEvent->m_pSyncEvent->Notify(1);
            pCancelingCBEvent->Release();
        }
    }
    if (!IsExistEvent(pSender, id, pHandlerThis, pHandlerFunc)) {
        _Elastos_CriticalSection_Leave(&m_queueLock, pContext);
        return NOERROR;
    }
    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

    // 'm_workingLock' be used waiting for msg loop idled
    Boolean bEntered = FALSE;
    _CCriticalSection_TryEnter(&m_workingLock, &bEntered);
    if (bEntered) _CCriticalSection_Leave(&m_workingLock);
    else return E_CALLBACK_IS_BUSY;

    return NOERROR;
}

void CCallbackContext::CancelAllCallbackEvents()
{
    void * pContext = _Elastos_CriticalSection_Enter(&m_queueLock);
    while (!m_eventQueue.IsEmpty()) {
        PCallbackEvent pCallbackEvent = (PCallbackEvent)m_eventQueue.First();

        pCallbackEvent->Detach();
        --m_nEventsCount;

        pCallbackEvent->m_Status = CallingStatus_Cancelled;
        pCallbackEvent->m_bCompleted = TRUE;
        if (pCallbackEvent->m_pSyncEvent) pCallbackEvent->m_pSyncEvent->Notify(1);
        pCallbackEvent->Release();

    }

    _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

}

PCallbackEvent CCallbackContext::GetEvent(Flags32 fPriority)
{
    DLinkNode* pCurt = m_eventQueue.First();
    if (fPriority) {
        while (pCurt != &m_eventQueue) {
            PCallbackEvent pCallbackEvent = (PCallbackEvent)pCurt;
            if ((fPriority & CallbackEventFlag_PriorityMask) <
                ((Flags32)pCallbackEvent->m_flags & CallbackEventFlag_PriorityMask)) {
                if ((fPriority & ~CallbackEventFlag_PriorityMask)==0
                    || ((fPriority & ~CallbackEventFlag_PriorityMask)
                            & pCallbackEvent->m_flags)){
                    pCurt->Detach();
                    return (PCallbackEvent)pCurt;
                }
            }

            pCurt = pCurt->Next();
        }
    }
    else {
        pCurt->Detach();
        return (PCallbackEvent)pCurt;
    }

    return NULL;
}

QuitCode CCallbackContext::HandleCallbackEvents(
    Millisecond32 msTimeOut,
    WaitResult *pResult,
    Boolean* pbOccured,
    Flags32 fPriority)
{
    void * pContext;
    void * pWorkContext;
    Boolean bEmpty = FALSE;
    PCallbackEvent pCallbackEvent = NULL;
    IInterface* pClientObj = NULL;
    PVoid pLock = NULL;

    Millisecond32 msBegPoint = CSystem::GetElapsedTime();
    Millisecond32 msLapsedTime = 0;

    while (TRUE) {
        pContext = _Elastos_CriticalSection_Enter(&m_queueLock);

        if (m_bExitRequested) {
            if (!m_bRequestToQuit) {
                _Elastos_CriticalSection_Leave(&m_queueLock, pContext);
                if (pResult) *pResult = WaitResult_Interrupted;
                goto Exit;
            }
        }

        if (m_eventQueue.IsEmpty()) {
            bEmpty = TRUE;
        }
        else {
            pCallbackEvent = GetEvent(fPriority);
            if (pCallbackEvent == NULL) {
                bEmpty = TRUE;
            }
        }

        if (bEmpty) {
            bEmpty = FALSE;
            if (m_bRequestToQuit) {
                _Elastos_CriticalSection_Leave(&m_queueLock, pContext);
                if (pResult) *pResult = WaitResult_Interrupted;
                goto Exit;
            }
            m_Status = AppletStatus_Idling;
            _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

            WaitResult wr;
            m_pThreadEvent->TryWait(msTimeOut - msLapsedTime, &wr, NULL);
            if (pbOccured) {
                if (*pbOccured) {
                    if (pResult) *pResult = WaitResult_OK;
                    *pbOccured = FALSE;
                    goto Exit;
                }
            }
        }
        else {
            --m_nEventsCount;
            assert(pCallbackEvent);
            PushEvent(pCallbackEvent);
            pWorkContext = _Elastos_CriticalSection_Enter(&m_workingLock);
            m_Status = AppletStatus_Working;
            _Elastos_CriticalSection_Leave(&m_queueLock, pContext);

            assert(pCallbackEvent->m_pHandleRoutine);
            if (NULL != pCallbackEvent->m_pHandlerFunc) {
                PVoid pDomain = NULL;
                _CThread_GetLocalValue(TL_DOMAIN_SLOT, (TLValue*)&pDomain);
                _CThread_SetLocalValue(TL_DOMAIN_SLOT,
                                        (TLValue)pCallbackEvent->m_pDomain);

                pClientObj = NULL;
                if (CallbackType_CAR == pCallbackEvent->m_typeOfFunc) {
                    pClientObj = pCallbackEvent->m_pReceiver;
                    if (pClientObj->AddRef() == 1) goto Cancel;
                    pClientObj->Aggregate(AggrType_FriendEnter, (PInterface)&pLock);
                }

                (*pCallbackEvent->m_pHandleRoutine)(pCallbackEvent);

                if (pClientObj) {
                    pClientObj->Aggregate(AggrType_FriendLeave, (PInterface)pLock);
                    pClientObj->Release();
                }
Cancel:
                _CThread_SetLocalValue(TL_DOMAIN_SLOT, (TLValue)pDomain);
            }
            _Elastos_CriticalSection_Leave(&m_workingLock, pWorkContext);


            PopEvent();
            pCallbackEvent->Release();

            if (pbOccured) {
                if (*pbOccured) {
                    if (pResult) *pResult = WaitResult_OK;
                    *pbOccured = FALSE;
                    goto Exit;
                }
            }
        }

        if (msTimeOut != INFINITE) {
            msLapsedTime = CSystem::GetElapsedTime() - msBegPoint;
            if (msLapsedTime > msTimeOut) {
                if (pResult) *pResult = WaitResult_TimedOut;
                goto Exit;
            }
        }
    }


Exit:
    return 0;
}
